import chisel3._
import chisel3.util.DecoupledIO

object Config {
  def hasPerCounters: Boolean = true
  def synthesize: Boolean = false
}

class TopIO(dataWidth: Int, addrWidth: Int) extends Bundle {
  // val inst    = Input(UInt(dataWidth.W))
  // val addr    = Output(UInt(addrWidth.W))
  // val done    = Output(Bool())
  val interrupt = Input(Bool())
  val master    = new MessageAXI4MasterInterface(dataWidth)
  val slave     = Flipped(new MessageAXI4MasterInterface(dataWidth))
}

class ysyx_23060189(val dataWidth: Int, val addrWidth: Int) extends Module {
  val io = IO(new TopIO(dataWidth, addrWidth))

  val ifu     = Module(new IFU(dataWidth))
  val idu     = Module(new IDU(dataWidth))
  val exu     = Module(new EXU(dataWidth))
  val meu     = Module(new MEU(dataWidth))
  val wbu     = Module(new WBU(dataWidth))

  val mem     = Module(new MemAccess(dataWidth, addrWidth))
  val arbiter = Module(new AXI4Arbiter(dataWidth))
  val icache  = Module(new ICache(dataWidth, 8, 16))
  // val xbar    = Module(new Xbar(dataWidth))
  // val clint   = Module(new CLINT(dataWidth))

  val mem_to_arbiter_master_interface    = Module(new AXI4MasterInterface(dataWidth))
  // val icache_to_arbiter_master_interface = Module(new AXI4MasterInterface(dataWidth))
  // val xbar_to_clint_slave_interface    = Module(new AxiSlaveInterface(dataWidth))

  StageConnect(ifu.io.out, idu.io.in)
  StageConnect(idu.io.out, exu.io.in)
  StageConnect(exu.io.out, meu.io.in)
  StageConnect(meu.io.out, wbu.io.in)
  StageConnect(wbu.io.out, ifu.io.in)
  ifu.io.br_taken      := meu.io.br_taken
  ifu.io.Alu_out       := meu.io.Alu_out
  idu.io.in_wb.wb_sel  := wbu.io.wb_sel
  idu.io.in_wb.wb_en   := wbu.io.out.bits.wb_en
  idu.io.in_wb.pc      := wbu.io.pc
  idu.io.in_wb.csr_out := wbu.io.out.bits.csr_out
  idu.io.in_wb.Alu_out := wbu.io.out.bits.Alu_out
  idu.io.in_wb.wb_addr := wbu.io.wb_addr
  idu.io.in_wb.rd_data := wbu.io.rd_data
  idu.io.in_wb.valid   := wbu.io.out.valid
  idu.io.meu_wb_en     := meu.io.out.bits.wb_en
  idu.io.meu_wb_sel    := meu.io.out.bits.wb_sel
  idu.io.meu_wb_Alu_out:= meu.io.out.bits.Alu_out
  idu.io.meu_rd_data   := meu.io.out.bits.rd_data
  idu.io.meu_wb_addr   := meu.io.out.bits.wb_addr
  idu.io.meu_out_valid := meu.io.meu_valid
  idu.io.flush         := meu.io.br_taken
  idu.io.exu_wb_en     := exu.io.out.bits.wb_en
  idu.io.exu_wb_sel    := exu.io.out.bits.wb_sel
  idu.io.exu_Alu_out   := exu.io.out.bits.Alu_out
  idu.io.exu_wb_addr   := exu.io.out.bits.wb_addr
  idu.io.exu_out_valid := exu.io.out.valid
  exu.io.br_taken      := meu.io.br_taken

  io.slave.awready := 0.B
  io.slave.wready  := 0.B
  io.slave.bvalid  := 0.B
  io.slave.bresp   := 0.U
  io.slave.bid     := 0.U
  io.slave.arready := 0.B
  io.slave.rvalid  := 0.B
  io.slave.rresp   := 0.U
  io.slave.rdata   := 0.U
  io.slave.rlast   := 0.U
  io.slave.rid     := 0.U

  // ifu <=> icache
  icache.io.addr   := ifu.io.addr
  icache.io.avalid := ifu.io.avalid
  icache.io.rready := ifu.io.rready
  ifu.io.data      := icache.io.data
  ifu.io.aready    := icache.io.aready
  ifu.io.rvalid    := icache.io.rvalid

  // idu <=> icache
  icache.io.inst       := idu.io.out.bits.inst
  icache.io.inst_valid := idu.io.out.valid

  // meu <=> mem
  mem.io.meu_valid := meu.io.meu_valid
  mem.io.addr      := meu.io.addr
  mem.io.st_type   := meu.io.st_type
  mem.io.ld_type   := meu.io.ld_type
  mem.io.wr_data   := meu.io.wr_data
  meu.io.rd_data   := mem.io.rd_data
  meu.io.mem_valid := mem.io.mem_valid

  // icache <=> arbiter
  icache.io.ma <> arbiter.io.slave1
  // mem <=> master interface
  mem.io.mm <> mem_to_arbiter_master_interface.io.mm
  // master interface <=> arbiter
  mem_to_arbiter_master_interface.io.ma    <> arbiter.io.slave2

  // arbiter <=> Xbar
  // arbiter.io.sa1 <> xbar.io.ax

  // arbiter <=> cpu master
  arbiter.io.master1 <> io.master

  // Xbar <=> clint slave interface
  // xbar.io.xbar_clint <> xbar_to_clint_slave_interface.io.sa
  // clint slave interface <=> clint
  // xbar_to_clint_slave_interface.io.ss <> clint.io.sc

  ifu.io.ACLK    := clock
  ifu.io.ARESETn := !(reset.asBool)

  icache.io.ACLK    := clock
  icache.io.ARESETn := !(reset.asBool)

  mem.io.ACLK    := clock
  mem.io.ARESETn := !(reset.asBool)

  arbiter.io.ACLK    := clock
  arbiter.io.ARESETn := !(reset.asBool)

  // xbar.io.ACLK    := clock
  // xbar.io.ARESETn := !(reset.asBool)

  // clint.io.ACLK    := clock
  // clint.io.ARESETn := !(reset.asBool)

  mem_to_arbiter_master_interface.io.ACLK     :=  clock
  mem_to_arbiter_master_interface.io.ARESETn  := !(reset.asBool)

  // xbar_to_clint_slave_interface.io.ACLK    :=  clock
  // xbar_to_clint_slave_interface.io.ARESETn := !(reset.asBool)

  // performence counters
  if (Config.hasPerCounters) {
    val per_counters = Module(new PerCounters)
    per_counters.io.inst := ifu.io.out.bits.inst

    per_counters.io.ifu_ready := ifu.io.rvalid & ifu.io.rready

    per_counters.io.icache_ren    := icache.io.ma.arvalid
    per_counters.io.icache_rready := icache.io.ma.rready
    per_counters.io.icache_rvalid := icache.io.ma.rvalid
    per_counters.io.icache_rlast  := icache.io.ma.rlast

    per_counters.io.idu_st_type := idu.io.out.bits.st_type
    per_counters.io.idu_ld_type := idu.io.out.bits.ld_type
    per_counters.io.idu_br_type := idu.io.out.bits.br_type
    per_counters.io.idu_valid   := idu.io.out.valid

    per_counters.io.meu_rvalid := mem_to_arbiter_master_interface.io.ma.rvalid
    per_counters.io.meu_rready := mem_to_arbiter_master_interface.io.ma.rready
    per_counters.io.meu_wvalid := mem_to_arbiter_master_interface.io.ma.wvalid
    per_counters.io.meu_wready := mem_to_arbiter_master_interface.io.ma.wready

    per_counters.io.meu_valid  := meu.io.meu_valid

    per_counters.io.exu_valid   := exu.io.in.valid
    per_counters.io.exu_Alu_op  := exu.io.in.bits.Alu_op
    per_counters.io.exu_csr_cmd := exu.io.in.bits.csr_cmd

    per_counters.io.ifu_valid := ifu.io.out.valid
    per_counters.io.wb_valid  := ifu.io.in.valid
    per_counters.io.ifu_ren   := ifu.io.avalid & ifu.io.aready

    // per_counters.io.br_taken  := wbu.io.out.bits.br_taken
    per_counters.io.br_taken  := exu.io.br_taken
  }
}

object StageConnect {
  def apply[T <: Data](left: DecoupledIO[T], right:DecoupledIO[T]) = {
    val arch = "multi"

    if (arch == "single") {right.bits := left.bits}
    else if (arch == "multi") {right <> left}
  }
}
